import java.util.ArrayList;
import java.util.BitSet;
import java.util.Set;
import java.util.TreeSet;


public class Individual implements Comparable<Individual>
{
	private Chromosome chromosome;
	private Fenotipe fenotipe = null;;
	private double fitness = Double.NaN;
	public Individual clone()
	{
		BitSet b = (BitSet) this.getChromosomeBitSet().clone();
		return new Individual(new Chromosome(b));
	}
	public Individual()
	{
		this.chromosome = Data.getRandomChromosome();
		this.init(chromosome);
	}
	public Individual(Chromosome chromosome) {
		this.chromosome = chromosome;
		init(this.chromosome);
	}
	private void init( final Chromosome chromosome )
	{
		this.fenotipe = chromosome.getFenotipe();
		this.fitness = this.fenotipe.getFitness();
	}
	public double getCost()
	{
		return this.fenotipe.getFenotipeCost();
	}
	public int compareTo(Individual o)
	{		
		if( Math.abs(this.fitness - o.getFitness() ) < Data.EPS )			
			return this.toString().compareTo(o.toString());
		
		return this.fitness > o.getFitness() ? -1 : 1;
	}	
	public double getFitnessDebug()
	{
		return this.fenotipe.getFitnessDebug();
	}
	public double getFitness()
	{
		return this.fitness;
	}
	public String toString()
	{
		return "Individual(fitness = "+Util.format(this.chromosome.getFenotipe().getFitness(),30)+": \n"+this.chromosome.toString()+"\n"+this.fenotipe.toString();
	}
	public static ArrayList<Individual> crossOver(Individual mother, Individual father)
	{
		ArrayList<Individual> ans = new ArrayList<Individual>(2);
		if( Math.random() > Data.CROSSOVER_PROBABILITY )
		{
			ans.add( mother );
			ans.add( father );
			return ans;
		}
		
		BitSet a = mother.getChromosomeBitSet();
		BitSet b = father.getChromosomeBitSet();
		
		BitSet newA = new BitSet(Data.CHROMOSOME_SIZE);
		BitSet newB = new BitSet(Data.CHROMOSOME_SIZE);
		
		for (int index = 0; index < Data.TOTAL_BUILDINGS; index++)
		{
			if( index < Data.TOTAL_BUILDINGS/2 )
			{
				newA.set( index , a.get(index));
				newB.set( index , b.get(index));
			}
			else
			{
				newA.set( index , b.get(index));
				newB.set( index , a.get(index));
			}
		}
		
		for (int index = Data.TOTAL_BUILDINGS, d = 0; d < 7; index++ , d++)
		{
			if( d < 3 )
			{
				newA.set( index , a.get(index));
				newB.set( index , b.get(index));
			}
			else
			{
				newA.set( index , b.get(index));
				newB.set( index , a.get(index));
			}
		}		
		
		for (int index = Data.TOTAL_BUILDINGS+7; index < Data.CHROMOSOME_SIZE ; index++)
		{
			if( index < Data.TOTAL_BUILDINGS + 7 +  ( (Data.CHROMOSOME_SIZE-Data.TOTAL_BUILDINGS-7)/2 ) )
			{
				newA.set( index , a.get(index));
				newB.set( index , b.get(index));
			}
			else
			{
				newA.set( index , b.get(index));
				newB.set( index , a.get(index));
			}
		}
		Set<Boolean> values = new TreeSet<Boolean>();
		for (int index = Data.TOTAL_BUILDINGS,d = 0; d < 7; ++d,index++)					
			values.add( newA.get(index) ) ;
		if( values.size() == 1 )
			newA.flip(Data.TOTAL_BUILDINGS+3);
		
		values.clear();
		for (int index = Data.TOTAL_BUILDINGS,d = 0; d < 7; ++d,index++)					
			values.add( newB.get(index) ) ;
		if( values.size() == 1 )
			newB.flip(Data.TOTAL_BUILDINGS+3);
		
		
		ans.add( new Individual( new Chromosome( newA ) ) );
		ans.add( new Individual( new Chromosome( newB ) ) );
		return ans;
	}
	public BitSet getChromosomeBitSet()
	{
		return this.chromosome.getBitset();
	}
	public void mutate()
	{				
		if( Math.random() > Data.MUTATION_PROBABILITY )return;
		
		BitSet bits = this.chromosome.getBitset();
		int buildingToChange = Random.getRandomNumber(0, Data.TOTAL_BUILDINGS);
		bits.flip(buildingToChange);
		for (int index = 0; index < Data.TOTAL_BUILDINGS; index++)
			if( Math.random() > Data.MUTATION_PROBABILITY )
				bits.flip(index);
		
		for (int day = 0, index = Data.TOTAL_BUILDINGS; day < 7; ++day,++index)
			if( Math.random() > Data.MUTATION_PROBABILITY )
				bits.flip(index);
		
		for (int index = Data.TOTAL_BUILDINGS+7; index < Data.CHROMOSOME_SIZE; ++index)
			if( Math.random() > 3*Data.MUTATION_PROBABILITY )
				bits.set(index,false);
		
		
		this.chromosome = new Chromosome(bits);
		this.init(this.chromosome);		
	}
	public Fenotipe getFenotipe()
	{
		return this.fenotipe;
	}
	public int[][] getTrucks()
	{
		return this.fenotipe.getTrucks();
	}
}
